package xj.graph2d.shapes;

import static xj.graph2d.DrawShapeConstants.PointInBounds;
import static xj.graph2d.DrawShapeConstants.PointOutside;

import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.Dimension2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.util.HashMap;
import java.util.Map;

import xj.graph2d.DrawShape;
import xj.graph2d.Label;
import xj.graph2d.RectangleShape;
import xj.graph2d.graph.Dimension2DFloat;
import xj.graph2d.graph.Geometry;
import xj.util.gui.Point2DFloat;

public class RectangularShape 
  extends RectangleShape 
  implements ShapeConstants {

  public RectangularShape() {
    this(BOX_SHAPE, NORTH, false);
  }

  public RectangularShape(int shapeType) {
    this(shapeType, NORTH, false);
  }

  public RectangularShape(int shapeType, int direction, boolean createComp) {
    super(false, createComp);
    this.shapeType = shapeType;
    this.direction = direction;
    clipSubshapes = (shapeType != ACTOR_SHAPE && shapeType != TERMINAL_SHAPE);
  }

  public RectangularShape(int shapeType, int direction, float x1, float y1,
			  float x2, float y2) {
    this(shapeType, direction, x1, y1, x2, y2, false);
  }

  public RectangularShape(int shapeType, int direction, float x1, float y1,
			  float x2, float y2, boolean createComp) {
    super(x1, y1, x2, y2, false, createComp);
    this.shapeType = shapeType;
    this.direction = direction;
    clipSubshapes = (shapeType != ACTOR_SHAPE && shapeType != TERMINAL_SHAPE);
  }

  @Override
  public DrawShape makeInstance() {
    return new RectangularShape(shapeType, direction, comp != null);
  }

  public static String toShapeKey(int shapeType) {
    if (shapeType >= 0 && shapeType < shapeKeys.length) {
      return shapeKeys[shapeType];
    }
    return null;
  }

  public static int toShapeType(String key) {
    if (key != null) {
      for (int i = 0; i < shapeKeys.length; i++) {
	if (key.equals(shapeKeys[i])) {
	  return i;
	}
      }
    }
    return -1;
  }

  @Override
  public String getName() {
    if (shapeType >= 0 && shapeType < shapeKeys.length) {
      return shapeKeys[shapeType];
    } else {
      return "RectangularShape";
    }
  }

  public int getShapeType() {
    return shapeType;
  }

  public void setShapeType(int shapeType) {
    this.shapeType = shapeType;
    positionLabel();
    adjustContent();
  }

  public int getDirection() {
    return direction;
  }

  public void setDirection(int direction) {
    this.direction = direction;
    positionLabel();
    adjustContent();
  }

  protected Point2D[] getBoundingPoints() {
    if (shapeType >= 0 && shapeType < points.length) {
      int n = points[shapeType].length;
      int np = n / 2;

      float x = (float) Math.min(p1.getX(), p2.getX());
      float y = (float) Math.min(p1.getY(), p2.getY());
      float w = (float) Math.abs(p1.getX() - p2.getX());
      float h = (float) Math.abs(p1.getY() - p2.getY());

      Point2D[] result = new Point2D[np];
      // float[] ptsx = new float[np];
      // float[] ptsy = new float[np];
      for (int i = 0; i < n; i += 2) {
	float px = points[shapeType][i];
	float py = points[shapeType][i + 1];
	if (direction == SOUTH) {
	  py = 1 - py;
	} else if (direction == EAST) {
	  if (shapeType != RIGHT_TRIANGLE_SHAPE && shapeType != DBOX_SHAPE) {
	    float t = px;
	    px = py;
	    py = t;
	  }
	  px = 1 - px;
	} else if (direction == WEST) {
	  float t = px;
	  px = py;
	  py = t;
	}
	// ptsx[i / 2] = x + w * px;
	// ptsy[i / 2] = y + h * py;
	result[i / 2] = new Point2DFloat(x + w * px, y + h * py);
      }
      return result;
    }
    return null;
  }

  @Override
  public void draw(Graphics2D g2) {
    // System.out.println("RectangularShape.draw(): clipSubshapes=" +
    // clipSubshapes);
    drawBase(g2, false);
  }

  @Override
  public void drawOutline(Graphics2D g2) {
    drawBase(g2, true);
  }

  public void drawBase(Graphics2D g2, boolean outline) {
    if (shapeType >= 0 && shapeType < points.length) {
      int n = points[shapeType].length;
      int np = n / 2;

      float x = (float) Math.min(p1.getX(), p2.getX());
      float y = (float) Math.min(p1.getY(), p2.getY());
      float w = (float) Math.abs(p1.getX() - p2.getX());
      float h = (float) Math.abs(p1.getY() - p2.getY());

      float[] ptsx = new float[np];
      float[] ptsy = new float[np];
      for (int i = 0; i < n; i += 2) {
	float px = points[shapeType][i];
	float py = points[shapeType][i + 1];
	if (direction == SOUTH) {
	  py = 1 - py;
	} else if (direction == EAST) {
	  if (shapeType != RIGHT_TRIANGLE_SHAPE && shapeType != DBOX_SHAPE) {
	    float t = px;
	    px = py;
	    py = t;
	  }
	  px = 1 - px;
	} else if (direction == WEST) {
	  float t = px;
	  px = py;
	  py = t;
	}
	ptsx[i / 2] = x + w * px;
	ptsy[i / 2] = y + h * py;
      }

      if (shapeType <= OCTAGON_SHAPE) {
	GeneralPath polygon = new GeneralPath(Path2D.WIND_EVEN_ODD, np);
	polygon.moveTo(ptsx[0], ptsy[0]);
	for (int i = 1; i < np; i++) {
	  polygon.lineTo(ptsx[i], ptsy[i]);
	}
	polygon.closePath();
	if (filled && !outline) {
	  fillBackground(g2, x, y, w, h, polygon);
	  if (showborder) {
	    g2.draw(polygon);
	  }
	} else {
	  g2.draw(polygon);
	}
	outlineShape = polygon;
      } else if (shapeType == ACTOR_SHAPE) {
	Ellipse2D ellipse = new Ellipse2D.Float(Math.min(ptsx[0], ptsx[1]), 
						Math.min(ptsy[0], ptsy[1]), 
						Math.abs(ptsx[1] - ptsx[0]), 
						Math.abs(ptsy[1] - ptsy[0]));
	GeneralPath gpath = new GeneralPath();
	gpath.moveTo(ptsx[2], ptsy[2]);
	gpath.lineTo(ptsx[3], ptsy[3]); 
	gpath.lineTo(ptsx[4], ptsy[4]);
	gpath.moveTo(ptsx[3], ptsy[3]);
	gpath.lineTo(ptsx[5], ptsy[5]);
	gpath.moveTo(ptsx[6], ptsy[6]);
	gpath.lineTo(ptsx[7], ptsy[7]);
	gpath.moveTo(ptsx[2], ptsy[2]);
	gpath.append(ellipse, false);

	if (filled && !outline) {
	  fillBackground(g2, x, y, w, h, gpath);
	  /*
	  if (showborder) {
	    g2.draw(gpath);
	  }
	  */
	}
	g2.draw(gpath);
	Stroke st = g2.getStroke();	
	outlineShape = st.createStrokedShape(gpath);

	/*
	g2.draw(new Ellipse2D.Float(Math.min(ptsx[0], ptsx[1]), 
				    Math.min(ptsy[0], ptsy[1]), 
				    Math.abs(ptsx[1] - ptsx[0]), 
				    Math.abs(ptsy[1] - ptsy[0])));
	g2.draw(new Line2D.Float(ptsx[2], ptsy[2], ptsx[3], ptsy[3]));
	g2.draw(new Line2D.Float(ptsx[3], ptsy[3], ptsx[4], ptsy[4]));
	g2.draw(new Line2D.Float(ptsx[3], ptsy[3], ptsx[5], ptsy[5]));
	g2.draw(new Line2D.Float(ptsx[6], ptsy[6], ptsx[7], ptsy[7]));
	*/
      } else if (shapeType == TERMINAL_SHAPE) {
	GeneralPath polygon = new GeneralPath();
	polygon.moveTo(ptsx[0], ptsy[0]);
	for (int i = 1; i <= 5; i++) {
	  polygon.lineTo(ptsx[i], ptsy[i]);
	}
	polygon.closePath();
	if (filled && !outline) {
	  fillBackground(g2, x, y, w, h, polygon);
	  if (showborder) {
	    g2.draw(polygon);
	  }
	} else {
	  g2.draw(polygon);
	}
	outlineShape = polygon;
	g2.draw(new Line2D.Float(ptsx[1], ptsy[1], ptsx[4], ptsy[4]));
	g2.draw(new RoundRectangle2D.Float(Math.min(ptsx[6], ptsx[7]), 
					   Math.min(ptsy[6], ptsy[7]), 
					   Math.abs(ptsx[6] - ptsx[7]), 
					   Math.abs(ptsy[6] - ptsy[7]), 
					   Math.abs(ptsx[6] - ptsx[7]) / 6, 
					   Math.abs(ptsy[6] - ptsy[7]) / 6));
      } else if (shapeType == NOTES_SHAPE) {
	GeneralPath polygon = new GeneralPath();
	polygon.moveTo(ptsx[0], ptsy[0]);
	for (int i = 1; i <= 4; i++) {
	  polygon.lineTo(ptsx[i], ptsy[i]);
	}
	polygon.closePath();
	if (filled && !outline) {
	  fillBackground(g2, x, y, w, h, polygon);
	  if (showborder) {
	    g2.draw(polygon);
	  }
	} else {
	  g2.draw(polygon);
	}
	outlineShape = polygon;
	g2.draw(new Line2D.Float(ptsx[5], ptsy[5], ptsx[6], ptsy[6]));
	g2.draw(new Line2D.Float(ptsx[6], ptsy[6], ptsx[7], ptsy[7]));
      } else if (shapeType == DBOX_SHAPE) {
	GeneralPath polygon = new GeneralPath();
	polygon.moveTo(ptsx[0], ptsy[0]);
	for (int i = 1; i <= 5; i++) {
	  polygon.lineTo(ptsx[i], ptsy[i]);
	}
	polygon.closePath();
	if (filled && !outline) {
	  fillBackground(g2, x, y, w, h, polygon);
	  if (showborder) {
	    g2.draw(polygon);
	  }
	} else {
	  g2.draw(polygon);
	}
	outlineShape = polygon;
	g2.draw(new Line2D.Float(ptsx[0], ptsy[0], ptsx[6], ptsy[6]));
	g2.draw(new Line2D.Float(ptsx[6], ptsy[6], ptsx[4], ptsy[4]));
	g2.draw(new Line2D.Float(ptsx[6], ptsy[6], ptsx[2], ptsy[2]));
      } else {
	g2.draw(new Rectangle2D.Float(x, y, w, h));
      }
    }
  }

  @Override
  public Shape getOutlineShape() {
    if (outlineShape != null) {
      return outlineShape;
    } else {
      return super.getBoundingRect();
    }
  }

  /* return the intersection point of p and the center of the node */
  @Override
  public Point2D intersect(Point2D p, float offsetX, float offsetY) {
    if (p != null) {
      Point2D result = null;
      if (shapeType >= 0 && shapeType <= points.length) {
	Point2D[] pts = getBoundingPoints();
	if (pts != null) {
	  int n = 0;
	  if (shapeType <= OCTAGON_SHAPE) {
	    n = pts.length;
	  } else if (shapeType == DBOX_SHAPE) {
	    n = 6;
	  } else if (shapeType == NOTES_SHAPE) {
	    n = 5;
	  }
	  if (n > 0) {
	    Point2D c = getCenter();
	    c.setLocation(c.getX() + offsetX, c.getY() + offsetY);
	    if (isInside(p) == PointOutside) {
	      // find the intersection closer to p
	      result = Geometry.intersectionPolygonLine(pts, n, p, c);
	    } else {
	      // find the intersection closer to c
	      result = Geometry.intersectionPolygonLine(pts, n, c, p);
	    }
	  }
	}
      }
      if (result != null) {
	return result;
      } else {
	return super.intersect(p, offsetX, offsetY);
      }
    }
    return null;
  }

  @Override
  public Dimension2D fitContentDimension() {
    Dimension2D dim = getContentDimension();
    float w = 0;
    float h = 0;
    if (dim != null) {
      w = (float) dim.getWidth();
      h = (float) dim.getHeight();
    }
    switch (shapeType) {
    case ACTOR_SHAPE:
    case TERMINAL_SHAPE:
      return null;
    case DBOX_SHAPE:
      switch (direction) {
      case NORTH:
      case SOUTH:
      case EAST:
      case WEST:
	return new Dimension2DFloat(w / 0.8f + 2 * nodePad, h / 0.8f + 2 * nodePad);
      }
    case TRIANGLE_SHAPE:
    case RIGHT_TRIANGLE_SHAPE: // to be fixed
      switch (direction) {
      case NORTH:
      case SOUTH:
      case EAST:
      case WEST:
	return new Dimension2DFloat(2 * w + nodePad, 2 * h + nodePad);
      }
    case DIAMOND_SHAPE:
      return new Dimension2DFloat(2 * w + nodePad, 2 * h + nodePad);
    case TRAPEZOID_SHAPE:
      switch (direction) {
      case NORTH:
      case SOUTH:
      case EAST:
      case WEST:
	return new Dimension2DFloat(w / 0.8f + 2 * nodePad, h / 0.8f + 2 * nodePad);
      }
    case PARALLELOGRAM_SHAPE:
      switch (direction) {
      case NORTH:
      case SOUTH:
      case EAST:
      case WEST:
	return new Dimension2DFloat(w / 0.8f + 2 * nodePad, h / 0.8f + 2 * nodePad);
      }
    case HOUSE_SHAPE:
    case PENTAGON_SHAPE:
      switch (direction) {
      case NORTH:
      case SOUTH:
	return new Dimension2DFloat(w + 2 * nodePad, h / 0.8f + 2 * nodePad);
      case EAST:
      case WEST:
	return new Dimension2DFloat(w / 0.8f + 2 * nodePad, h + 2 * nodePad);
      }
    case CAVE_SHAPE:
      switch (direction) {
      case NORTH:
      case SOUTH:
	return new Dimension2DFloat(w + 2 * nodePad, h / 0.8f + 2 * nodePad);
      case EAST:
      case WEST:
	return new Dimension2DFloat(w / 0.7f + 2 * nodePad, h + 2 * nodePad);
      }
    case HEXAGON_SHAPE:
      switch (direction) {
      case NORTH:
      case SOUTH:
      case EAST:
      case WEST:
	return new Dimension2DFloat(w / 0.8f + 2 * nodePad, h + 2 * nodePad);
      }
    case OCTAGON_SHAPE:
      switch (direction) {
      case NORTH:
      case SOUTH:
      case EAST:
      case WEST:
	return new Dimension2DFloat(w / 0.9f + 2 * nodePad, h + 2 * nodePad);
      }
    default:
      // return super.fitContentDimension();
      return new Dimension2DFloat(w + 2 * nodePad, h + 2 * nodePad);
    }
  }

  @Override
  public void positionLabel() {
    Label label = findLabel();
    if (label != null) {
      if (fitLabel) { 
	adjustContent(false);
      }

      label.calculateBounds();

      if (bounds == null) {
	calculateBounds();
      }

      float x = (float) Math.min(p1.getX(), p2.getX());
      float y = (float) Math.min(p1.getY(), p2.getY());
      float w = (float) Math.abs(p1.getX() - p2.getX());
      float h = (float) Math.abs(p1.getY() - p2.getY());
      Rectangle2D b = label.getBounds();
      float tw = (float) b.getWidth();
      float th = (float) b.getHeight();
      switch (shapeType) {
      case ACTOR_SHAPE:
      case TERMINAL_SHAPE:
	label.setPosition(x + (w - tw) / 2, y + h + nodePad);
	break;
      case DBOX_SHAPE:
	switch (direction) {
	case NORTH:
	  label.setPosition(x + (w * 0.8f - tw) / 2, y + (h * 1.2f - th) / 2);
	  break;
	case SOUTH:
	  label.setPosition(x + (w * 0.8f - tw) / 2, y + (h * 0.8f - th) / 2);
	  break;
	case EAST:
	  label.setPosition(x + (w * 0.8f - tw) / 2, y + (h * 0.8f - th) / 2);
	  break;
	case WEST:
	  label.setPosition(x + (w * 1.2f - tw) / 2, y + (h * 0.8f - th) / 2);
	  break;
	}
	break;
      case TRIANGLE_SHAPE:
	switch (direction) {
	case NORTH:
	  label.setPosition(x + (w - tw) / 2, y + h / 2 + (h / 2 - th) / 2);
	  break;
	case SOUTH:
	  label.setPosition(x + (w - tw) / 2, y + h / 2 - (h / 2 + th) / 2);
	  break;
	case EAST:
	  label.setPosition(x + w / 2 - (w / 2 + tw) / 2, y + (h - th) / 2);
	  break;
	case WEST:
	  label.setPosition(x + w / 2 - (w / 2 - tw) / 2, y + (h - th) / 2);
	  break;
	}
	break;
      case DIAMOND_SHAPE:
	label.setPosition(x + (w - tw) / 2, y + (h - th) / 2);
	break;
      case TRAPEZOID_SHAPE:
	switch (direction) {
	case NORTH:
	  label.setPosition(x + (w - tw) / 2, y + (h * 1.2f - th) / 2);
	  break;
	case SOUTH:
	  label.setPosition(x + (w - tw) / 2, y + (h * 0.8f - th) / 2);
	  break;
	case EAST:
	  label.setPosition(x + (w * 0.8f - tw) / 2, y + (h - th) / 2);
	  break;
	case WEST:
	  label.setPosition(x + (w * 1.2f - tw) / 2, y + (h - th) / 2);
	  break;
	}
	break;
      case PARALLELOGRAM_SHAPE:
	switch (direction) {
	case NORTH:
	case SOUTH:
	case EAST:
	case WEST:
	  label.setPosition(x + (w * 0.8f - tw) / 2 + w * 0.1f, y + (h - th) / 2);
	}
	break;
      case HOUSE_SHAPE:
	switch (direction) {
	case NORTH:
	  label.setPosition(x + (w - tw) / 2, y + (h * 1.2f - th) / 2);
	  break;
	case SOUTH:
	  label.setPosition(x + (w - tw) / 2, y + (h * 0.8f - th) / 2);
	  break;
	case EAST:
	  label.setPosition(x + (w * 0.8f - tw) / 2, y + (h - th) / 2);
	  break;
	case WEST:
	  label.setPosition(x + (w * 1.2f - tw) / 2, y + (h - th) / 2);
	  break;
	}
	break;
      case CAVE_SHAPE:
	switch (direction) {
	case NORTH:
	  label.setPosition(x + (w - tw) / 2, y + (h * 1.2f - th) / 2);
	  break;
	case SOUTH:
	  label.setPosition(x + (w - tw) / 2, y + (h * 0.8f - th) / 2);
	  break;
	case EAST:
	  label.setPosition(x + (w * 0.7f - tw) / 2, y + (h - th) / 2);
	  break;
	case WEST:
	  label.setPosition(x + (w * 1.3f - tw) / 2, y + (h - th) / 2);
	  break;
	}
	break;
      case HEXAGON_SHAPE:
	switch (direction) {
	case NORTH:
	case SOUTH:
	case EAST:
	case WEST:
	  label.setPosition(x + (w * 0.8f - tw) / 2 + w * 0.1f, y + (h - th)
	      / 2);
	}
	break;
      case OCTAGON_SHAPE:
	switch (direction) {
	case NORTH:
	case SOUTH:
	case EAST:
	case WEST:
	  label.setPosition(x + (w * 0.9f - tw) / 2 + w * 0.05f, y + (h - th)
	      / 2);
	}
	break;
      case NOTES_SHAPE:
	label.setPosition(x + nodePad, y + nodePad);
	break;
      default:
	// super.positionLabel();
	label.setPosition(x + (w - tw) / 2, y + (h - th) / 2);
      }
    }
  }

  @Override
  public int isInside(float px, float py) {
    int result = super.isInside(px, py);
    if (result == PointOutside && 
	(shapeType == ACTOR_SHAPE || shapeType == TERMINAL_SHAPE)) {
      if (framebounds != null && 
	  framebounds.contains(px, py)) {
	result = PointInBounds;
      }
    }
    return result;
  }

  @Override
  public Point2D getULCorner() {
    if ((shapeType == ACTOR_SHAPE || shapeType == TERMINAL_SHAPE) && 
	bounds != null) {
      return new Point2DFloat((float) bounds.getX(), (float) bounds.getY());
    } else {
      return p1;
    }
  }

  @Override
  public Point2D getLRCorner() {
    if ((shapeType == ACTOR_SHAPE || shapeType == TERMINAL_SHAPE) && 
	bounds != null) {
      return new Point2DFloat((float) (bounds.getX() + bounds.getWidth()),
			      (float) (bounds.getY() + bounds.getHeight()));
    } else {
      return p2;
    }
  }

  @Override
  protected Map<String, Object> getXMLAttributes() {
    Map<String, Object> attrs = super.getXMLAttributes();
    if (attrs == null) {
      attrs = new HashMap<String, Object>();
    }
    attrs.put("type", shapeType);
    attrs.put("direction", direction);
    return attrs;
  }

  @Override
  protected void processXMLAttributes(org.w3c.dom.Element node) {
    if (node != null) {
      super.processXMLAttributes(node);

      String s;
      s = node.getAttribute("type");
      try {
	shapeType = Integer.parseInt(s);
      } catch (NumberFormatException ex) {
      }
      s = node.getAttribute("direction");
      try {
	direction = Integer.parseInt(s);
      } catch (NumberFormatException ex) {
      }
    }
  }

  protected int shapeType;

  protected int direction;

  protected Shape outlineShape = null;

}
